package daniel.weck;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;

/**
 * http://astroboid.x50.cc/blog/2010/06/mapping-box2d-shapes-from-textures/ A
 * mostly "blind" C# conversion of the TextureConverter code from Farseer, I
 * even left the original german comments in place. Seems to work.
 */
public class TextureConverter {
	// User contribution from Sickbattery
	// / <summary>
	// / TODO:
	// / 1.) Das Array welches ich bekomme am besten in einen boolean array
	// verwandeln. Wurde die Geschwindigkeit verbessern
	// / </summary>
	private static int[][] ClosePixels = new int[][] { { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } };

	public static Array<Vector2> createPolygon(int[] data, int width, int height) throws Exception {
		PolygonCreationAssistance pca = new PolygonCreationAssistance(data, width, height);
		Array<Array<Vector2>> verts = createPolygon(pca);
		return verts.get(0);
	}

	public static Array<Vector2> createPolygon(int[] data, int width, int height, boolean holeDetection) throws Exception {
		PolygonCreationAssistance pca = new PolygonCreationAssistance(data, width, height);
		pca.HoleDetection = holeDetection;
		Array<Array<Vector2>> verts = createPolygon(pca);
		return verts.get(0);
	}

	public static Array<Array<Vector2>> createPolygon(int[] data, int width, int height, float hullTolerance, byte alphaTolerance,
			boolean multiPartDetection, boolean holeDetection) throws Exception {
		PolygonCreationAssistance pca = new PolygonCreationAssistance(data, width, height);
		pca.setHullTolerance(hullTolerance);
		pca.setAlphaTolerance(alphaTolerance);
		pca.MultipartDetection = multiPartDetection;
		pca.HoleDetection = holeDetection;
		return createPolygon(pca);
	}

	private static Array<Array<Vector2>> createPolygon(PolygonCreationAssistance pca) throws Exception {
		Array<Array<Vector2>> polygons = new Array<Array<Vector2>>();
		Array<Vector2> polygon;
		Array<Vector2> holePolygon;
		Vector2 holeEntrance = null;
		Vector2 polygonEntrance = null;
		Array<Vector2> blackList = new Array<Vector2>();
		// First of all: Check the array you just got.
		if (pca.IsValid()) {
			boolean searchOn;
			do {
				if (polygons.size == 0) {
					polygon = CreateSimplePolygon(pca, new Vector2(), new Vector2());
					if (polygon != null && polygon.size > 2) {
						polygonEntrance = GetTopMostVertex(polygon);
					}
				} else if (polygonEntrance != null) {
					polygon = CreateSimplePolygon(pca, polygonEntrance, new Vector2(polygonEntrance.x - 1f, polygonEntrance.y));
				} else {
					break;
				}
				searchOn = false;
				if (polygon != null && polygon.size > 2) {
					if (pca.HoleDetection) {
						do {
							holeEntrance = GetHoleHullEntrance(pca, polygon, holeEntrance);
							if (holeEntrance != null) {
								if (!vectorListContains(blackList, holeEntrance)) {
									blackList.add(holeEntrance);
									holePolygon = CreateSimplePolygon(pca, holeEntrance, new Vector2(holeEntrance.x + 1, holeEntrance.y));
									if (holePolygon != null && holePolygon.size > 2) {
										holePolygon.add(holePolygon.get(0));
										Reference<Integer> vertex2IndexRef = new Reference<Integer>(0);
										Reference<Integer> vertex1IndexRef = new Reference<Integer>(0);
										if (SplitPolygonEdge(polygon, EdgeAlignment.Vertical, holeEntrance, vertex1IndexRef,
												vertex2IndexRef)) {

											polygon.ensureCapacity(holePolygon.size);
											for (int i = holePolygon.size - 1; i <= 0; i--) {
												polygon.insert(vertex2IndexRef.v, holePolygon.get(i));
											}
										}
									}
								} else {
									break;
								}
							} else {
								break;
							}
						} while (true);
					}
					polygons.add(polygon);
					if (pca.MultipartDetection) {
						// 1: 95 / 151
						// 2: 232 / 252
						//
						polygonEntrance = new Vector2();
						while (GetNextHullEntrance(pca, polygonEntrance, polygonEntrance)) {
							boolean inPolygon = false;
							for (int i = 0; i < polygons.size; i++) {
								polygon = polygons.get(i);
								if (InPolygon(pca, polygon, polygonEntrance)) {
									inPolygon = true;
									break;
								}
							}
							if (!inPolygon) {
								searchOn = true;
								break;
							}
						}
					}
				}
			} while (searchOn);
		} else {
			throw new Exception("Sizes don't match: Color array must contain texture width * texture height elements.");
		}
		return polygons;
	}

	private static Vector2 GetHoleHullEntrance(PolygonCreationAssistance pca, Array<Vector2> polygon, Vector2 startVertex) throws Exception {
		Array<CrossingEdgeInfo> edges = new Array<CrossingEdgeInfo>();
		Vector2 entrance;
		int startLine;
		int endLine;
		int lastSolid = 0;
		boolean foundSolid;
		boolean foundTransparent;
		if (polygon != null && polygon.size > 0) {
			if (startVertex != null) {
				startLine = (int) startVertex.y;
			} else {
				startLine = (int) GetTopMostCoord(polygon);
			}
			endLine = (int) GetBottomMostCoord(polygon);
			if (startLine > 0 && startLine < pca.Height && endLine > 0 && endLine < pca.Height) {
				// go from top to bottom of the polygon
				for (int y = startLine; y <= endLine; y += pca.getHoleDetectionLineStepSize()) {
					// get x-coord of every polygon edge which crosses y
					edges = GetCrossingEdges(polygon, EdgeAlignment.Vertical, y);
					// we need an even number of crossing edges
					if (edges.size > 1 && edges.size % 2 == 0) {
						for (int i = 0; i < edges.size; i += 2) {
							foundSolid = false;
							foundTransparent = false;
							for (int x = (int) edges.get(i).CrossingPoint.x; x <= (int) edges.get(i + 1).CrossingPoint.x; x++) {
								if (pca.IsSolid(x, y)) {
									if (!foundTransparent) {
										foundSolid = true;
										lastSolid = x;
									}
									if (foundSolid && foundTransparent) {
										entrance = new Vector2(lastSolid, y);
										if (DistanceToHullAcceptable(pca, polygon, entrance, true)) {
											return entrance;
										}
										entrance = null;
										break;
									}
								} else {
									if (foundSolid) {
										foundTransparent = true;
									}
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	private static boolean DistanceToHullAcceptable(PolygonCreationAssistance pca, Array<Vector2> polygon, Vector2 point,
			boolean higherDetail) {
		if (polygon != null && polygon.size > 2) {
			Vector2 edgeVertex2 = polygon.get(polygon.size - 1).cpy();
			Vector2 edgeVertex1 = new Vector2();
			if (higherDetail) {
				for (int i = 0; i < polygon.size; i++) {
					edgeVertex1.set(polygon.get(i));
					if (LineTools.DistanceBetweenPointAndLineSegment(point, edgeVertex1, edgeVertex2) <= pca.getHullTolerance()
							|| LineTools.DistanceBetweenPointAndPoint(point, edgeVertex1) <= pca.getHullTolerance()) {
						return false;
					}
					edgeVertex2.set(polygon.get(i));
				}
				return true;
			} else {
				for (int i = 0; i < polygon.size; i++) {
					edgeVertex1.set(polygon.get(i));
					if (LineTools.DistanceBetweenPointAndLineSegment(point, edgeVertex1, edgeVertex2) <= pca.getHullTolerance()) {
						return false;
					}
					edgeVertex2.set(polygon.get(i));
				}
				return true;
			}
		}
		return false;
	}

	private static boolean InPolygon(PolygonCreationAssistance pca, Array<Vector2> polygon, Vector2 point) throws Exception {
		boolean inPolygon = !DistanceToHullAcceptable(pca, polygon, point, true);
		if (!inPolygon) {
			Array<CrossingEdgeInfo> edges = GetCrossingEdges(polygon, EdgeAlignment.Vertical, (int) point.y);
			if (edges.size > 0 && edges.size % 2 == 0) {
				for (int i = 0; i < edges.size; i += 2) {
					if (edges.get(i).CrossingPoint.x <= point.x && edges.get(i + 1).CrossingPoint.x >= point.x) {
						return true;
					}
				}
				return false;
			}
			return false;
		}
		return true;
	}

	private static Vector2 GetTopMostVertex(Array<Vector2> vertices) {
		float topMostValue = Float.MAX_VALUE;
		Vector2 topMost = null;
		for (int i = 0; i < vertices.size; i++) {
			if (topMostValue > vertices.get(i).y) {
				topMostValue = vertices.get(i).y;
				topMost = vertices.get(i);
			}
		}
		return topMost.cpy();
	}

	private static float GetTopMostCoord(Array<Vector2> vertices) {
		float returnValue = Float.MAX_VALUE;
		for (int i = 0; i < vertices.size; i++) {
			if (returnValue > vertices.get(i).y) {
				returnValue = vertices.get(i).y;
			}
		}
		return returnValue;
	}

	private static float GetBottomMostCoord(Array<Vector2> vertices) {
		float returnValue = Float.MIN_VALUE;
		for (int i = 0; i < vertices.size; i++) {
			if (returnValue < vertices.get(i).y) {
				returnValue = vertices.get(i).y;
			}
		}
		return returnValue;
	}

	public static Boolean vectorEquals(Vector2 v1, Vector2 v2) {
		return v1.x == v2.x && v1.y == v2.y;
	}

	public static int vectorListIndexOf(Array list, Vector2 v) {
		for (int i = 0; i < list.size; i++) {
			Object obj = list.get(i);
			if (obj == v)
				return i;
			if (obj instanceof Vector2) {
				Vector2 vect = (Vector2) obj;
				if (vectorEquals(v, vect))
					return i;
			}
		}
		return -1;
	}

	public static Boolean vectorListContains(Array list, Vector2 v) {
		int index = vectorListIndexOf(list, v);
		return index != -1;
	}

	public static Vector2 vectorSub(Vector2 v1, Vector2 v2) {
		return new Vector2(v1.x - v2.x, v1.y - v2.y);
	}

	public static Vector2 vectorAdd(Vector2 v1, Vector2 v2) {
		return new Vector2(v1.x + v2.x, v1.y + v2.y);
	}

	public static float vectorCross(Vector2 v1, Vector2 v2) {
		return v1.x * v2.y - v1.y * v2.x;
	}

	public static Vector2 vectorCross(Vector2 v1, float scalar) {
		return new Vector2(scalar * v1.y, -scalar * v1.x);
	}

	public static float vectorDot(Vector2 v1, Vector2 v2) {
		return v1.x * v2.x + v1.y * v2.y;
	}

	public static Vector2 vectorMul(Vector2 v1, float scalar) {
		return new Vector2(v1.x * scalar, v1.y * scalar);
	}

	private static Array<CrossingEdgeInfo> GetCrossingEdges(Array<Vector2> polygon, EdgeAlignment edgeAlign, int checkLine)
			throws Exception {
		Array<CrossingEdgeInfo> edges = new Array<CrossingEdgeInfo>();
		Vector2 slope = new Vector2();
		Vector2 edgeVertex1 = new Vector2();
		Vector2 edgeVertex2 = new Vector2();
		Vector2 slopePreview = new Vector2();
		Vector2 edgeVertexPreview = new Vector2();
		Vector2 crossingPoint = new Vector2();
		boolean addCrossingPoint;
		if (polygon.size > 1) {
			edgeVertex2.set(polygon.get(polygon.size - 1));
			switch (edgeAlign) {
			case Vertical:
				for (int i = 0; i < polygon.size; i++) {
					edgeVertex1.set(polygon.get(i));
					if ((edgeVertex1.y >= checkLine && edgeVertex2.y <= checkLine)
							|| (edgeVertex1.y <= checkLine && edgeVertex2.y >= checkLine)) {
						if (edgeVertex1.y != edgeVertex2.y) {
							addCrossingPoint = true;
							slope.set(vectorSub(edgeVertex2, edgeVertex1));
							if (edgeVertex1.y == checkLine) {
								edgeVertexPreview.set(polygon.get((i + 1) % polygon.size));
								slopePreview.set(vectorSub(edgeVertex1, edgeVertexPreview));
								if (slope.y > 0) {
									addCrossingPoint = (slopePreview.y <= 0);
								} else {
									addCrossingPoint = (slopePreview.y >= 0);
								}
							}
							if (addCrossingPoint) {
								crossingPoint = new Vector2((checkLine - edgeVertex1.y) / slope.y * slope.x + edgeVertex1.x, checkLine);
								edges.add(new CrossingEdgeInfo(edgeVertex1, edgeVertex2, crossingPoint, edgeAlign));
							}
						}
					}
					edgeVertex2.set(edgeVertex1);
				}
				break;
			case Horizontal:
				throw new Exception("EdgeAlignment.Horizontal isn't implemented yet. Sorry.");
			}
		}
		edges.sort();
		// Collections.sort(edges);
		return edges;
	}

	private static boolean SplitPolygonEdge(Array<Vector2> polygon, EdgeAlignment edgeAlign, Vector2 coordInsideThePolygon,
			Reference<Integer> vertex1IndexRef, Reference<Integer> vertex2IndexRef) throws Exception {
		Array<CrossingEdgeInfo> edges;
		Vector2 slope = new Vector2();
		int nearestEdgeVertex1Index = 0;
		int nearestEdgeVertex2Index = 0;
		boolean edgeFound = false;
		float shortestDistance = Float.MAX_VALUE;
		boolean edgeCoordFound = false;
		Vector2 foundEdgeCoord = new Vector2();
		vertex1IndexRef.v = 0;
		vertex2IndexRef.v = 0;
		switch (edgeAlign) {
		case Vertical:
			edges = GetCrossingEdges(polygon, EdgeAlignment.Vertical, (int) coordInsideThePolygon.y);
			foundEdgeCoord.y = coordInsideThePolygon.y;
			if (edges != null && edges.size > 1 && edges.size % 2 == 0) {
				float distance;
				for (int i = 0; i < edges.size; i++) {
					if (edges.get(i).CrossingPoint.x < coordInsideThePolygon.x) {
						distance = coordInsideThePolygon.x - edges.get(i).CrossingPoint.x;
						if (distance < shortestDistance) {
							shortestDistance = distance;
							foundEdgeCoord.x = edges.get(i).CrossingPoint.x;
							edgeCoordFound = true;
						}
					}
				}
				if (edgeCoordFound) {
					shortestDistance = Float.MAX_VALUE;
					int edgeVertex2Index = polygon.size - 1;
					int edgeVertex1Index;
					for (edgeVertex1Index = 0; edgeVertex1Index < polygon.size; edgeVertex1Index++) {
						Vector2 tempVector1 = polygon.get(edgeVertex1Index).cpy();
						Vector2 tempVector2 = polygon.get(edgeVertex2Index).cpy();
						distance = LineTools.DistanceBetweenPointAndLineSegment(foundEdgeCoord, tempVector1, tempVector2);
						if (distance < shortestDistance) {
							shortestDistance = distance;
							nearestEdgeVertex1Index = edgeVertex1Index;
							nearestEdgeVertex2Index = edgeVertex2Index;
							edgeFound = true;
						}
						edgeVertex2Index = edgeVertex1Index;
					}
					if (edgeFound) {
						slope.set(vectorSub(polygon.get(nearestEdgeVertex2Index), polygon.get(nearestEdgeVertex1Index)));
						slope.nor();
						Vector2 tempVector = polygon.get(nearestEdgeVertex1Index).cpy();
						distance = LineTools.DistanceBetweenPointAndPoint(tempVector, foundEdgeCoord);
						vertex1IndexRef.v = nearestEdgeVertex1Index;
						vertex2IndexRef.v = nearestEdgeVertex1Index + 1;
						// distance * slope + polygon[vertex1Index]
						polygon.insert(nearestEdgeVertex1Index, vectorAdd(vectorMul(slope, distance), polygon.get(vertex1IndexRef.v)));
						polygon.insert(nearestEdgeVertex1Index, vectorAdd(vectorMul(slope, distance), polygon.get(vertex2IndexRef.v)));
						return true;
					}
				}
			}
			break;
		case Horizontal:
			throw new Exception("EdgeAlignment.Horizontal isn't implemented yet. Sorry.");
		}
		return false;
	}

	private static Array<Vector2> CreateSimplePolygon(PolygonCreationAssistance pca, Vector2 entrance, Vector2 last) {
		boolean entranceFound = false;
		boolean endOfHull = false;
		Array<Vector2> polygon = new Array<Vector2>();
		Array<Vector2> hullArea = new Array<Vector2>();
		Array<Vector2> endOfHullArea = new Array<Vector2>();
		Vector2 current = new Vector2();
		Vector2 zeroVec = new Vector2();
		// Get the entrance point. //todo: alle moglichkeiten testen
		if (vectorEquals(entrance, zeroVec) || !pca.InBounds(entrance)) {
			entranceFound = GetHullEntrance(pca, entrance);
			if (entranceFound) {
				current.set(entrance.x - 1f, entrance.y);
			}
		} else {
			if (pca.IsSolid(entrance)) {
				if (IsNearPixel(pca, entrance, last)) {
					current.set(last);
					entranceFound = true;
				} else {
					Vector2 temp = new Vector2();
					if (SearchNearPixels(pca, false, entrance, temp)) {
						current.set(temp);
						entranceFound = true;
					} else {
						entranceFound = false;
					}
				}
			}
		}
		if (entranceFound) {
			polygon.add(entrance);
			hullArea.add(entrance);
			Vector2 next = entrance.cpy();
			do {
				// Search in the pre vision list for an outstanding point.
				Vector2 outstanding = new Vector2();
				if (SearchForOutstandingVertex(hullArea, pca.getHullTolerance(), outstanding)) {
					if (endOfHull) {
						// We have found the next pixel, but is it on the last
						// bit of the
						// hull?
						if (vectorListContains(endOfHullArea, outstanding) && !vectorListContains(polygon, outstanding)) {
							// Indeed.
							polygon.add(outstanding);
						}
						// That's enough, quit.
						break;
					}
					// Add it and remove all vertices that don't matter anymore
					// (all the vertices before the outstanding).
					polygon.add(outstanding);
					int index = vectorListIndexOf(hullArea, outstanding);
					if (index == -1) {
						int debug = 1;
					}
					if (index >= 0) {
						// hullArea = hullArea.subList(index + 1,
						// hullArea.size);

						// Array<Vector2> newArray = new Array<Vector2>
						// (hullArea.size - (index + 1));
						int counter = 0;
						for (int i = index + 1; i < hullArea.size; i++) {
							Vector2 v = hullArea.get(index);
							// newArray.add(v);
							hullArea.set(counter, v);
							counter++;
						}
						// hullArea.clear();
						// hullArea = newArray;
						for (int i = 0; i < index + 1; i++) {
							hullArea.pop();
						}
					}
				}
				// Last point gets current and current gets next. Our little
				// spider is
				// moving forward on the hull ;).
				last.set(current);
				current.set(next);
				// Get the next point on hull.
				next = new Vector2();
				if (GetNextHullPoint(pca, last, current, next)) {
					// Add the vertex to a hull pre vision list.
					hullArea.add(next);
				} else {
					// Quit
					break;
				}
				if (vectorEquals(next, entrance) && !endOfHull) {
					// It's the last bit of the hull, search on and exit at next
					// found
					// vertex.
					endOfHull = true;
					endOfHullArea.addAll(hullArea);
				}
			} while (true);
		}
		return polygon;
	}

	private static boolean SearchNearPixels(PolygonCreationAssistance pca, boolean searchingForSolidPixel, Vector2 current,
			Vector2 foundPixel) {
		int x;
		int y;
		for (int i = 0; i < 8; i++) {
			x = (int) current.x + ClosePixels[i][0];
			y = (int) current.y + ClosePixels[i][1];
			if (!searchingForSolidPixel ^ pca.IsSolid(x, y)) {
				foundPixel.set(x, y);
				return true;
			}
		}
		// Nothing found.
		foundPixel.set(0, 0);
		return false;
	}

	private static boolean IsNearPixel(PolygonCreationAssistance pca, Vector2 current, Vector2 near) {
		for (int i = 0; i < 8; i++) {
			int x = (int) current.x + ClosePixels[i][0];
			int y = (int) current.y + ClosePixels[i][1];
			if (x >= 0 && x <= pca.Width && y >= 0 && y <= pca.Height) {
				if (x == (int) near.x && y == (int) near.y) {
					return true;
				}
			}
		}
		return false;
	}

	private static boolean GetHullEntrance(PolygonCreationAssistance pca, Vector2 entrance) {
		// Search for first solid pixel.
		for (int y = 0; y < pca.Height; y++) {
			for (int x = 0; x < pca.Width; x++) {
				if (pca.IsSolid(x, y)) {
					entrance.set(x, y);
					return true;
				}
			}
		}
		// If there are no solid pixels.
		entrance.set(0, 0);
		return false;
	}

	private static boolean GetNextHullEntrance(PolygonCreationAssistance pca, Vector2 start, Vector2 entrance) {
		// Search for first solid pixel.
		int size = pca.Height * pca.Width;
		int x;
		boolean foundTransparent = false;
		for (int i = (int) start.x + (int) start.y * pca.Width; i < size; i++) {
			if (pca.IsSolid(i)) {
				if (foundTransparent) {
					x = i % pca.Width;
					entrance.set(x, (i - x) / pca.Width);
					return true;
				}
			} else {
				foundTransparent = true;
			}
		}
		// If there are no solid pixels.
		entrance.set(0, 0);
		return false;
	}

	private static boolean GetNextHullPoint(PolygonCreationAssistance pca, Vector2 last, Vector2 current, Vector2 next) {
		int x;
		int y;
		int indexOfFirstPixelToCheck = GetIndexOfFirstPixelToCheck(last, current);
		int indexOfPixelToCheck;
		int pixelsToCheck = 8; // _closePixels.Length;
		for (int i = 0; i < pixelsToCheck; i++) {
			indexOfPixelToCheck = (indexOfFirstPixelToCheck + i) % pixelsToCheck;
			x = (int) current.x + ClosePixels[indexOfPixelToCheck][0];
			y = (int) current.y + ClosePixels[indexOfPixelToCheck][1];
			if (x >= 0 && x < pca.Width && y >= 0 && y <= pca.Height) {
				if (pca.IsSolid(x, y)) // todo
				{
					next.set(x, y);
					return true;
				}
			}
		}
		next.set(0, 0);
		return false;
	}

	private static boolean SearchForOutstandingVertex(Array<Vector2> hullArea, float hullTolerance, Vector2 outstanding) {
		Vector2 outstandingResult = new Vector2();
		boolean found = false;
		if (hullArea.size > 2) {
			int hullAreaLastPoint = hullArea.size - 1;
			Vector2 tempVector1;
			Vector2 tempVector2 = hullArea.get(0);
			Vector2 tempVector3 = hullArea.get(hullAreaLastPoint);
			// Search between the first and last hull point.
			for (int i = 1; i < hullAreaLastPoint; i++) {
				tempVector1 = hullArea.get(i);
				// Check if the distance is over the one that's tolerable.
				if (LineTools.DistanceBetweenPointAndLineSegment(tempVector1, tempVector2, tempVector3) >= hullTolerance) {
					outstandingResult.set(hullArea.get(i));
					found = true;
					break;
				}
			}
		}
		outstanding.set(outstandingResult);
		return found;
	}

	private static int GetIndexOfFirstPixelToCheck(Vector2 last, Vector2 current) {
		// .: pixel
		// l: last position
		// c: current position
		// f: first pixel for next search
		// f . .
		// l c .
		// . . .
		// Calculate in which direction the last move went and decide over the
		// next
		// first pixel.
		switch ((int) (current.x - last.x)) {
		case 1:
			switch ((int) (current.y - last.y)) {
			case 1:
				return 1;
			case 0:
				return 0;
			case -1:
				return 7;
			}
			break;
		case 0:
			switch ((int) (current.y - last.y)) {
			case 1:
				return 2;
			case -1:
				return 6;
			}
			break;
		case -1:
			switch ((int) (current.y - last.y)) {
			case 1:
				return 3;
			case 0:
				return 4;
			case -1:
				return 5;
			}
			break;
		}
		return 0;
	}
}

enum EdgeAlignment {
	Vertical, Horizontal
}

class CrossingEdgeInfo implements Comparable<CrossingEdgeInfo> {
	private EdgeAlignment _alignment;
	private Vector2 _crossingPoint;
	private Vector2 _edgeVertex2;
	private Vector2 _egdeVertex1;
	public Vector2 EdgeVertex1;
	public Vector2 EdgeVertex2;
	public EdgeAlignment CheckLineAlignment;
	public Vector2 CrossingPoint;

	public CrossingEdgeInfo(Vector2 edgeVertex1, Vector2 edgeVertex2, Vector2 crossingPoint, EdgeAlignment checkLineAlignment) {
		_egdeVertex1 = edgeVertex1.cpy();
		_edgeVertex2 = edgeVertex2.cpy();
		_alignment = checkLineAlignment;
		_crossingPoint = crossingPoint;
	}

	public int compareTo(CrossingEdgeInfo obj) {
		CrossingEdgeInfo cei = obj;
		int result = 0;
		switch (_alignment) {
		case Vertical:
			if (_crossingPoint.x < cei.CrossingPoint.y) {
				result = -1;
			} else if (_crossingPoint.x > cei.CrossingPoint.y) {
				result = 1;
			}
			break;
		case Horizontal:
			if (_crossingPoint.y < cei.CrossingPoint.y) {
				result = -1;
			} else if (_crossingPoint.y > cei.CrossingPoint.y) {
				result = 1;
			}
			break;
		}
		return result;
	}
}

// / <summary>
// / Class used as a data container and helper for the texture-to-vertices code.
// / </summary>
class PolygonCreationAssistance {
	private byte _alphaTolerance;
	private int _alphaToleranceRealValue;
	private int _holeDetectionLineStepSize;
	private float _hullTolerance;

	public PolygonCreationAssistance(int[] data, int width, int height) {
		Data = data;
		Width = width;
		Height = height;
		setAlphaTolerance((byte) 20);
		setHullTolerance(1.5f);
		setHoleDetectionLineStepSize(1);
		HoleDetection = false;
		MultipartDetection = false;
	}

	private int[] Data;
	public int Width;
	public int Height;

	public int getAlphaTolerance() {
		return _alphaTolerance;
	}

	public void setAlphaTolerance(byte value) {
		_alphaTolerance = value;
		_alphaToleranceRealValue = (int) value << 24;
	}

	public float getHullTolerance() {
		return _hullTolerance;
	}

	public void setHullTolerance(float value) {
		float hullTolerance = value;
		if (hullTolerance > 4f)
			hullTolerance = 4f;
		if (hullTolerance < 0.9f)
			hullTolerance = 0.9f;
		_hullTolerance = hullTolerance;
	}

	public int getHoleDetectionLineStepSize() {
		return _holeDetectionLineStepSize;
	}

	private void setHoleDetectionLineStepSize(int value) {
		if (value < 1) {
			_holeDetectionLineStepSize = 1;
		} else {
			if (value > 10) {
				_holeDetectionLineStepSize = 10;
			} else {
				_holeDetectionLineStepSize = value;
			}
		}
	}

	public boolean HoleDetection;
	public boolean MultipartDetection;

	public boolean IsSolid(Vector2 pixel) {
		return IsSolid((int) pixel.x, (int) pixel.y);
	}

	public boolean IsSolid(int x, int y) {
		if (x >= 0 && x < Width && y >= 0 && y < Height) {
			int data = Data[x + y * Width];
			long mask1 = (long) data & 0xFFFFFFFFL;
			long mask2 = mask1 & 0x000000FF;
			Boolean opaque = mask2 >= _alphaTolerance;
			if (opaque || mask2 != 0) {
				int debug = 1;
			}
			return opaque;
		}
		return false;
	}

	public boolean IsSolid(int index) {
		if (index >= 0 && index < Width * Height) {
			int data = Data[index];
			long mask1 = (long) data & 0xFFFFFFFFL;
			long mask2 = mask1 & 0x000000FF;
			Boolean opaque = mask2 >= _alphaTolerance;
			if (opaque || mask2 != 0) {
				int debug = 1;
			}
			return opaque;
			// return (mask1 >= _alphaToleranceRealValue);
		}
		return false;
	}

	public boolean InBounds(Vector2 coord) {
		return (coord.x >= 0f && coord.x < Width && coord.y >= 0f && coord.y < Height);
	}

	public boolean IsValid() {
		if (Data != null && Data.length > 0)
			return Data.length == Width * Height;
		return false;
	}
}

class Reference<K> {
	public K v;

	public Reference(K v) {
		this.v = v;
	}

	@Override
	public String toString() {
		return v.toString();
	}

	@Override
	public boolean equals(Object obj) {
		return v.equals(obj);
	}

	@Override
	public int hashCode() {
		return v.hashCode();
	}
}

class LineTools {
	public static float DistanceBetweenPointAndPoint(Vector2 point1, Vector2 point2) {
		Vector2 v = TextureConverter.vectorSub(point1, point2);
		return v.len();
	}

	public static float DistanceBetweenPointAndLineSegment(Vector2 point, Vector2 lineEndPoint1, Vector2 lineEndPoint2) {
		Vector2 v = TextureConverter.vectorSub(lineEndPoint2, lineEndPoint1);
		Vector2 w = TextureConverter.vectorSub(point, lineEndPoint1);
		float c1 = TextureConverter.vectorDot(w, v);
		if (c1 <= 0)
			return DistanceBetweenPointAndPoint(point, lineEndPoint1);
		float c2 = TextureConverter.vectorDot(v, v);
		if (c2 <= c1)
			return DistanceBetweenPointAndPoint(point, lineEndPoint2);
		float b = c1 / c2;
		Vector2 pointOnLine = TextureConverter.vectorAdd(lineEndPoint1, TextureConverter.vectorMul(v, b));
		return DistanceBetweenPointAndPoint(point, pointOnLine);
	}
}
